package com.nft.qz.datasource.convert;

import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 复制工具
 *
 * @since 2017/9/15
 */
@Slf4j
public class CopyConvertUtils {

    private CopyConvertUtils() {
    }


    /**
     * 复制source的属性到target的同名同类型属性上
     */
    public static <I, T> I copyProperties(T source, Class<I> clazz) {
        return copyProperties(source, clazz, false, null);
    }

    /**
     * 复制source的属性到target的同名同类型属性上
     */
    public static <I, T> I copyProperties(T source, Class<I> clazz, Convertor<T, I> convertor) {
        return copyProperties(source, clazz, false, null, convertor);
    }

    /**
     * 集合复制
     */
    public static <I, T> List<I> copyCollection(Collection<T> sources, Class<I> clazz) {
        return copyCollection(sources, clazz, null);
    }

    /**
     * 集合复制
     */
    public static <T, I> List<I> copyCollection(Collection<T> sources, Class<I> clazz, Convertor<T, I> convertor) {
        if(CollectionUtils.isEmpty(sources)) {
            return Lists.newArrayList();
        }
        Stream<I> iStream = sources.stream()
                .map(s -> copyProperties(s, clazz, convertor));

        return iStream.collect(Collectors.toList());
    }

    /**
     * 复制source的属性到target的同名同类型属性上
     *
     * @param ignoreProperties 不需要复制的属性名
     */
    public static <I, T> I copyProperties(T source, Class<I> clazz,
                                          Convertor<T, I> convertor, String... ignoreProperties) {
        return copyProperties(source, clazz, false, convertor, ignoreProperties);
    }

    /**
     * 复制source的非null属性到target的同名同类型属性上
     */
    public static <I, T> I copyNotNullProperies(T source, Class<I> clazz) {
        return copyProperties(source, clazz, true, null);
    }

    /**
     * 复制source的非null属性到target的同名同类型属性上
     */
    public static <I, T> I copyNotNullProperies(T source, Class<I> clazz,
                                                Convertor<T, I> convertor) {
        return copyProperties(source, clazz, true, convertor);
    }

    /**
     * 复制source的非null属性到target的同名同类型属性上
     *
     * @param source           源
     * @param clazz           目标
     * @param ignoreProperties 忽略的属性名
     * @param <T>              目标类型
     * @return 处理后的 target
     */
    public static <I, T> I copyNotNullProperties(T source, Class<I> clazz,
                                                 Convertor<T, I> convertor, String... ignoreProperties) {
        return copyProperties(source, clazz, true, convertor, ignoreProperties);
    }

    private static <I, T> I copyProperties(T source, Class<I> clazz, boolean ignoreNullValue,
                                           Convertor<T, I> convertor, String... ignoreProperties) {
        return copyProperties(source, clazz, ignoreNullValue, null, convertor, ignoreProperties);
    }


    /**
     * ,
     * String...)} 删除了真实类的参数 添加一个判断:是否复制null值 返回复制后的target,这是为了简化lambda写法
     *
     * @param ignoreNullValue  是否忽略null值复制,true表示忽略source中的null值属性
     * @param converterTable   属性转换表,为null的情况下不使用
     * @param ignoreProperties 要忽略的属性,这些属性不会被写入到目标对象
     */
    static <I, T> I copyProperties(T source,
                                   Class<I> clazz,
                                   boolean ignoreNullValue,
                                   Table<Class, Class, Converter> converterTable,
                                   Convertor<T, I> convertor,
                                   String... ignoreProperties) {
        Assert.notNull(source, "Source must not be null");
        I target = null;
        try {
            target = clazz.newInstance();
        } catch (Exception e) {
            log.error("复制失败", e);
            throw new RuntimeException(e);
        }
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(clazz);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : Collections.emptyList());
        for (PropertyDescriptor targetPd : targetPds) {
            if (ignoreList.contains(targetPd.getName())) {
                continue;
            }
            Method writeMethod = resolveBridgeMethod(targetPd.getWriteMethod());
            if (writeMethod == null) {
                continue;
            }
            PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd == null) {
                continue;
            }
            Method readMethod = resolveBridgeMethod(sourcePd.getReadMethod());
            if (readMethod == null) {
                continue;
            }
            Class sourcePropertyType = readMethod.getReturnType();
            Class targetPropertyType = writeMethod.getParameterTypes()[0];
            boolean useTable = converterTable != null && !converterTable.isEmpty();
            if (sourcePropertyType == Number.class && targetPropertyType == Long.class) {
                try {
                    Object value = readMethod.invoke(source);
                    //新逻辑
                    if (value == null && ignoreNullValue) {
                        continue;
                    }
                    writeMethod.invoke(target, ((Number) value).longValue());
                } catch (Throwable ex) {
                    throw new FatalBeanException(
                            "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                }
            } else if (sourcePropertyType == Number.class && targetPropertyType == Integer.class) {
                try {
                    Object value = readMethod.invoke(source);
                    //新逻辑
                    if (value == null && ignoreNullValue) {
                        continue;
                    }
                    writeMethod.invoke(target, ((Number) value).intValue());
                } catch (Throwable ex) {
                    throw new FatalBeanException(
                            "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                }

            } else if (ClassUtils.isAssignable(targetPropertyType, sourcePropertyType)) {
                try {
                    Object value = readMethod.invoke(source);
                    //新逻辑
                    if (value == null && ignoreNullValue) {
                        continue;
                    }
                    writeMethod.invoke(target, value);
                } catch (Throwable ex) {
                    throw new FatalBeanException(
                            "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                }
            } else if (useTable && converterTable.contains(sourcePropertyType, targetPropertyType)) {
                try {
                    Object value = readMethod.invoke(source);
                    //新逻辑
                    if (value == null && ignoreNullValue) {
                        continue;
                    }
                    Converter converter = converterTable.get(sourcePropertyType, targetPropertyType);
                    //noinspection unchecked
                    Object destValue = converter.convert(value);
                    writeMethod.invoke(target, destValue);
                } catch (Throwable ex) {
                    throw new FatalBeanException(
                            "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                }
            }
        }
        if (Objects.nonNull(convertor)) {
            target = convertor.convert(source, target);
        }
        return target;
    }

    private static WeakHashMap<Method, Method> bridgeMethodCache = new WeakHashMap<>();

    /**
     * 用于解决泛型属性取到的属性类型有误的问题
     */
    private static Method resolveBridgeMethod(Method sourceMethod) {
        if (sourceMethod == null) {
            return null;
        }
        if (sourceMethod.isBridge()) {
            return bridgeMethodCache.computeIfAbsent(sourceMethod, k -> BridgeMethodResolver.findBridgedMethod(sourceMethod));
        }
        return sourceMethod;
    }

}
